feat: Add Azure CLI auth source - #265
Conversation
Add azure_cli as a new identity type that delegates token acquisition to Azure CLI via azure-identity's AzureCliCredential. This allows tools calling fab to reuse an existing az login session instead of requiring a separate interactive fab auth login. Changes: - Add 'azure_cli' to AUTH_KEYS identity type allow-list - Add _acquire_token_from_azure_cli() using AzureCliCredential - Add --azure-cli flag to fab auth login - Add 'Azure CLI' option to interactive login menu - Show auth_source in fab auth status output - Add azure-identity>=1.15.0 dependency - Add 12 unit tests covering dispatch, scopes, errors, sanitization Security: error messages are sanitized to never leak token content. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a new authentication source (azure_cli) to Fabric CLI, delegating token acquisition to Azure CLI (via azure-identity’s AzureCliCredential) so fab can reuse an existing az login session. It also wires the new auth source into fab auth login (flag + interactive option) and exposes the selected auth source in fab auth status.
Changes:
- Add
azure_clias an allowed identity type and implement Azure CLI token acquisition inFabAuth. - Add
--azure-cliflag plus an “Azure CLI” interactive login option; includeauth_sourcein auth status output. - Add
azure-identity>=1.15.0dependency and a new unit test module for Azure CLI auth.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_core/test_fab_auth_azure_cli.py | Adds unit tests for the new Azure CLI auth flow, scopes, and error sanitization. |
| src/fabric_cli/parsers/fab_auth_parser.py | Adds --azure-cli to fab auth login and updates examples. |
| src/fabric_cli/core/fab_constant.py | Extends identity type allow-list to include azure_cli. |
| src/fabric_cli/core/fab_auth.py | Implements set_azure_cli() and _acquire_token_from_azure_cli() and dispatches in acquire_token(). |
| src/fabric_cli/commands/auth/fab_auth.py | Wires Azure CLI auth into login flows and adds auth_source to status output. |
| pyproject.toml | Adds the azure-identity runtime dependency required for AzureCliCredential. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (6)
tests/test_core/test_fab_auth_azure_cli.py:36
- These fixtures are unused in this test module, and the singleton-reset logic inside them is brittle (it relies on non-existent
__wrapped__and decorator internals). After resetting the FabAuth singleton in the autouse fixture, these can be removed to keep the tests easier to maintain.
@pytest.fixture
def auth_instance(temp_dir_fixture):
"""Get a fresh FabAuth instance."""
# Clear singleton for test isolation
FabAuth.__wrapped__ = None # type: ignore
from fabric_cli.core import fab_auth as fab_auth_module
if FabAuth in fab_auth_module.singleton.__wrapped__: # type: ignore
del fab_auth_module.singleton.__wrapped__[FabAuth] # type: ignore
return FabAuth()
tests/test_core/test_fab_auth_azure_cli.py:56
- This fixture is unused, and it attempts to monkeypatch
FabAuth.__init__.__globals__, which is not a safe or reliable way to reset the singleton (and may raise if executed). With the singleton reset handled in the autouse fixture, this block can be deleted.
@pytest.fixture
def fresh_auth(temp_dir_fixture, monkeypatch):
"""Get a fresh FabAuth instance with singleton cleared."""
# Reset singleton instances dict
import fabric_cli.core.fab_auth as auth_module
# Access the closure variable of the singleton decorator
singleton_instances = auth_module.singleton.__code__.co_consts # noqa
# Simpler approach: just patch the module-level reference
monkeypatch.setattr(
"fabric_cli.core.fab_auth.FabAuth.__init__.__globals__",
{},
raising=False,
)
# Re-instantiate
auth = FabAuth.__new__(FabAuth)
auth.__init__()
return auth
src/fabric_cli/core/fab_auth.py:444
- This line exceeds the repo’s Black line-length (88) and will be reformatted by CI (
tox.toml:69-70). Please wrap the conditional instantiation so the formatted output is stable and easier to read.
tenant_id = self.get_tenant_id()
try:
credential = AzureCliCredential(tenant_id=tenant_id) if tenant_id else AzureCliCredential()
# AzureCliCredential.get_token expects scopes as positional args
azure_token = credential.get_token(scope[0])
src/fabric_cli/core/fab_auth.py:461
- This sanitized error message is long enough to violate Black’s 88-char line length and will be reformatted by CI (
tox.toml:69-70). Splitting it across adjacent string literals keeps formatting stable.
error_msg = str(e)
if "accessToken" in error_msg or "token" in error_msg.lower():
error_msg = "Azure CLI token acquisition failed. Run 'az account get-access-token' manually to diagnose."
raise FabricCLIError(
src/fabric_cli/commands/auth/fab_auth.py:37
- When
--azure-cliis provided, other credential flags (e.g.,-u/-p,--certificate,--federated-token,--identity) are silently ignored due to branch precedence. This can lead to confusing CLI behavior; please validate and fail fast on incompatible combinations.
if getattr(args, "azure_cli", False):
FabAuth().set_access_mode("azure_cli", args.tenant)
FabAuth().set_azure_cli(args.tenant)
FabAuth().get_access_token(scope=fab_constant.SCOPE_FABRIC_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_ONELAKE_DEFAULT)
tests/test_core/test_fab_auth_azure_cli.py:24
- FabAuth is a singleton (see
@singletoninfabric_cli.core.fab_auth). These tests patchconfig_location()per-test, but without clearing the singleton,FabAuth()will reuse the first instance (and its first auth/cache paths), causing state leakage across tests and making the tmp_path isolation ineffective.
This issue also appears in the following locations of the same file:
- line 27
- line 39
@pytest.fixture(autouse=True)
def temp_dir_fixture(monkeypatch, tmp_path):
"""Create a temporary directory and configure FabAuth to use it."""
monkeypatch.setattr(
"fabric_cli.core.fab_state_config.config_location", lambda: str(tmp_path)
)
- Expand sanitization patterns (eyJ, Bearer, refresh_token, Authorization) - Auto-capture tenant from az account show at login - Tenant drift detection on every token acquisition - In-memory token caching by audience with 60s expiry buffer - Display tenant and auth mode at login and in auth status - Add 11 new tests (23 total) for drift, caching, sanitization Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (4)
src/fabric_cli/core/fab_auth.py:502
- _acquire_token_from_azure_cli() calls credential.get_token(scope[0]) even though the method signature allows an empty scope list. If scope is empty, this will raise IndexError instead of a FabricCLIError, and it also ignores additional scopes if ever provided.
credential = AzureCliCredential(tenant_id=stored_tenant) if stored_tenant else AzureCliCredential()
# AzureCliCredential.get_token expects scopes as positional args
azure_token = credential.get_token(scope[0])
token_result = {
"access_token": azure_token.token,
src/fabric_cli/commands/auth/fab_auth.py:35
- --azure-cli is not validated as mutually exclusive with managed identity/service principal flags. Because the code checks azure_cli first, a user can accidentally pass conflicting flags and silently get Azure CLI auth instead of an error.
if getattr(args, "azure_cli", False):
FabAuth().set_access_mode("azure_cli", args.tenant)
FabAuth().set_azure_cli(args.tenant)
FabAuth().get_access_token(scope=fab_constant.SCOPE_FABRIC_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_ONELAKE_DEFAULT)
tests/test_core/test_fab_auth_azure_cli.py:18
- FabAuth is a singleton (decorator returns a cached instance), but this autouse fixture only patches config_location/env vars and doesn’t clear the singleton cache. If any other test module instantiates FabAuth before this fixture runs, these tests will share state and potentially write auth/cache files outside tmp_path, causing order-dependent failures.
@pytest.fixture(autouse=True)
def temp_dir_fixture(monkeypatch, tmp_path):
"""Create a temporary directory and configure FabAuth to use it."""
monkeypatch.setattr(
"fabric_cli.core.fab_state_config.config_location", lambda: str(tmp_path)
src/fabric_cli/core/fab_auth.py:522
- The generic exception handler includes the raw exception message in the CLI error unless it matches a small allow-list of substrings. That does not guarantee token material won’t leak (e.g., access tokens that don’t contain the current patterns), which contradicts the PR’s “never leak token content” claim.
except Exception as e:
# Sanitize: never include token content in error messages
error_msg = str(e)
if any(p.lower() in error_msg.lower() for p in self._SENSITIVE_PATTERNS):
error_msg = "Azure CLI token acquisition failed. Run 'az account get-access-token' manually to diagnose."
Defer OneLake and Azure management token acquisition to first use, matching the lazy approach. Tokens are cached in-memory after first call. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (5)
src/fabric_cli/core/fab_auth.py:477
- Azure CLI auth introduces new
FabricCLIErrormessages as hardcoded strings. Elsewhere in this module, auth failures useErrorMessages.Auth.*()helpers for consistent wording and easier localization/maintenance. Consider moving these new messages intoErrorMessages.Authand reusing them here.
try:
from azure.identity import AzureCliCredential, CredentialUnavailableError
except ImportError:
raise FabricCLIError(
"Azure CLI auth requires the 'azure-identity' package. "
"Install it with: pip install azure-identity",
status_code=con.ERROR_AUTHENTICATION_FAILED,
)
tests/test_core/test_fab_auth_azure_cli.py:36
- These tests call
FabAuth()directly, butFabAuthis a singleton (via the@singletondecorator). Without reliably clearing the singleton cache per test, state (auth_file paths, env-loaded tokens, tenant id) can leak between tests and make behavior depend on execution order. The current fixture attempts (__wrapped__,singleton.__wrapped__) don't match how thesingletondecorator is implemented, so they won't actually reset anything.
@pytest.fixture
def auth_instance(temp_dir_fixture):
"""Get a fresh FabAuth instance."""
# Clear singleton for test isolation
FabAuth.__wrapped__ = None # type: ignore
from fabric_cli.core import fab_auth as fab_auth_module
if FabAuth in fab_auth_module.singleton.__wrapped__: # type: ignore
del fab_auth_module.singleton.__wrapped__[FabAuth] # type: ignore
return FabAuth()
src/fabric_cli/commands/auth/fab_auth.py:39
fab auth login --azure-clicurrently only acquires the Fabric token, while other login flows (interactive, SPN, managed identity) also acquire OneLake and Azure management tokens. This makes Azure CLI login behave differently and can leave later commands without the required secondary tokens.
if getattr(args, "azure_cli", False):
FabAuth().set_access_mode("azure_cli", args.tenant)
FabAuth().set_azure_cli(args.tenant)
FabAuth().get_access_token(scope=fab_constant.SCOPE_FABRIC_DEFAULT)
Context().context = FabAuth().get_tenant()
src/fabric_cli/core/fab_auth.py:500
- The Azure CLI token acquisition block includes lines that exceed the repo's Black line-length (88), which will cause formatting churn and makes the code harder to read (e.g., the inline conditional credential construction). Please wrap these statements in Black-friendly form.
try:
credential = AzureCliCredential(tenant_id=stored_tenant) if stored_tenant else AzureCliCredential()
# AzureCliCredential.get_token expects scopes as positional args
azure_token = credential.get_token(scope[0])
token_result = {
src/fabric_cli/core/fab_auth.py:522
- This sanitization fallback message is on a single very long line (over Black's 88-char limit). Wrapping it will keep formatting stable and improve readability.
error_msg = str(e)
if any(p.lower() in error_msg.lower() for p in self._SENSITIVE_PATTERNS):
error_msg = "Azure CLI token acquisition failed. Run 'az account get-access-token' manually to diagnose."
raise FabricCLIError(
f"Azure CLI authentication failed: {error_msg}",
All auth modes validate Fabric, OneLake, and Azure scopes at login. In-memory caching ensures no redundant subprocess calls at runtime. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
src/fabric_cli/core/fab_auth.py:501
- _acquire_token_from_azure_cli() calls scope[0] unconditionally (including in credential.get_token(scope[0])). If a caller passes an empty scope list, this will raise IndexError instead of a structured FabricCLIError. Also, AzureCliCredential.get_token expects scopes as positional args; using only scope[0] silently drops additional scopes if they’re ever introduced.
try:
credential = AzureCliCredential(tenant_id=stored_tenant) if stored_tenant else AzureCliCredential()
# AzureCliCredential.get_token expects scopes as positional args
azure_token = credential.get_token(scope[0])
token_result = {
tests/test_core/test_fab_auth_azure_cli.py:36
- The auth_instance fixture is unused and its singleton-reset logic is incorrect for FabAuth (FabAuth is a function returned by the singleton decorator, and singleton() doesn’t expose a wrapped instances map). Keeping this dead code is misleading and risks future test failures if someone starts using it.
@pytest.fixture
def auth_instance(temp_dir_fixture):
"""Get a fresh FabAuth instance."""
# Clear singleton for test isolation
FabAuth.__wrapped__ = None # type: ignore
from fabric_cli.core import fab_auth as fab_auth_module
if FabAuth in fab_auth_module.singleton.__wrapped__: # type: ignore
del fab_auth_module.singleton.__wrapped__[FabAuth] # type: ignore
return FabAuth()
tests/test_core/test_fab_auth_azure_cli.py:56
- The fresh_auth fixture is unused and attempts to mutate FabAuth.init.globals / singleton internals, which is brittle and not a valid way to reset the singleton. This should be removed to keep the test module deterministic and maintainable.
@pytest.fixture
def fresh_auth(temp_dir_fixture, monkeypatch):
"""Get a fresh FabAuth instance with singleton cleared."""
# Reset singleton instances dict
import fabric_cli.core.fab_auth as auth_module
# Access the closure variable of the singleton decorator
singleton_instances = auth_module.singleton.__code__.co_consts # noqa
# Simpler approach: just patch the module-level reference
monkeypatch.setattr(
"fabric_cli.core.fab_auth.FabAuth.__init__.__globals__",
{},
raising=False,
)
# Re-instantiate
auth = FabAuth.__new__(FabAuth)
auth.__init__()
return auth
Ensures both --azure-cli flag and interactive menu selection show the same confirmation message with tenant ID. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
src/fabric_cli/core/fab_auth.py:477
- New FabricCLIError messages here are hardcoded strings. Elsewhere in this module, auth errors consistently use ErrorMessages.Auth.* helpers (e.g., invalid_identity_type(), token_acquisition_failed(), access_token_error()), which centralizes wording and keeps UX consistent. Consider adding Azure CLI-specific ErrorMessages.Auth helpers and using them here (and for the other Azure CLI error branches) instead of inline strings.
raise FabricCLIError(
"Azure CLI auth requires the 'azure-identity' package. "
"Install it with: pip install azure-identity",
status_code=con.ERROR_AUTHENTICATION_FAILED,
)
src/fabric_cli/core/fab_auth.py:441
- In set_azure_cli(), identity_type is set before calling set_tenant(). If set_tenant() detects a tenant change it calls logout(), which clears _auth_info and can wipe out the just-set identity_type. This can leave the auth config without identity_type when switching tenants via set_azure_cli(tenant_id=...). Reorder so tenant changes (and any logout) happen first, then set identity_type last.
self._set_auth_properties(
{
con.IDENTITY_TYPE: "azure_cli",
}
)
tests/test_core/test_fab_auth_azure_cli.py:57
- The auth_instance and fresh_auth fixtures are defined but never used in this test module, and they contain brittle/incorrect attempts to reset the
@singleton-decoratedFabAuth (e.g., mutating FabAuth.wrapped and FabAuth.init.globals). Keeping these unused fixtures risks future accidental use and makes the tests harder to understand; remove them or refactor to a single, actually-used fixture.
@pytest.fixture
def auth_instance(temp_dir_fixture):
"""Get a fresh FabAuth instance."""
# Clear singleton for test isolation
FabAuth.__wrapped__ = None # type: ignore
from fabric_cli.core import fab_auth as fab_auth_module
if FabAuth in fab_auth_module.singleton.__wrapped__: # type: ignore
del fab_auth_module.singleton.__wrapped__[FabAuth] # type: ignore
return FabAuth()
@pytest.fixture
def fresh_auth(temp_dir_fixture, monkeypatch):
"""Get a fresh FabAuth instance with singleton cleared."""
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (5)
tests/test_core/test_fab_auth_azure_cli.py:57
- The
fresh_authfixture contains fragile/incorrect singleton-reset logic (e.g., readingsingleton.__code__.co_constsand patchingFabAuth.__init__.__globals__to{}), and it is unused in this test module. Leaving this in place makes the tests harder to understand and could break badly if someone starts using it later.
Consider removing it and relying on a single, correct autouse singleton-reset fixture instead.
@pytest.fixture
def fresh_auth(temp_dir_fixture, monkeypatch):
"""Get a fresh FabAuth instance with singleton cleared."""
# Reset singleton instances dict
import fabric_cli.core.fab_auth as auth_module
# Access the closure variable of the singleton decorator
singleton_instances = auth_module.singleton.__code__.co_consts # noqa
# Simpler approach: just patch the module-level reference
monkeypatch.setattr(
"fabric_cli.core.fab_auth.FabAuth.__init__.__globals__",
{},
raising=False,
)
# Re-instantiate
auth = FabAuth.__new__(FabAuth)
auth.__init__()
return auth
src/fabric_cli/core/fab_auth.py:477
- This new ImportError path raises
FabricCLIErrorwith a hardcoded message. In this codebase, auth errors are consistently sourced fromErrorMessages.Auth.*(see e.g.fab_auth.py:590-636) so messages stay centralized and reusable.
Please add an AuthErrors.azure_cli_missing_dependency() (or similar) and use it here for consistency.
try:
from azure.identity import AzureCliCredential, CredentialUnavailableError
except ImportError:
raise FabricCLIError(
"Azure CLI auth requires the 'azure-identity' package. "
"Install it with: pip install azure-identity",
status_code=con.ERROR_AUTHENTICATION_FAILED,
)
src/fabric_cli/core/fab_auth.py:513
- The Azure CLI unavailable case uses a hardcoded user-facing message. Elsewhere in this module, user-facing auth errors come from
ErrorMessages.Auth.*.
Consider adding a dedicated AuthErrors.azure_cli_unavailable() (and possibly a separate one for "not logged in") and using it here so error messaging stays consistent and maintainable.
except CredentialUnavailableError:
raise FabricCLIError(
"Azure CLI is not installed or not logged in. "
"Run 'az login' to authenticate, then retry.",
status_code=con.ERROR_AUTHENTICATION_FAILED,
)
src/fabric_cli/core/fab_auth.py:500
- This
credential = ... if ... else ...line exceeds Black’s default line length and will likely fail formatting checks in CI.
Wrap it onto multiple lines so black src/ tests/ stays clean.
try:
credential = AzureCliCredential(tenant_id=stored_tenant) if stored_tenant else AzureCliCredential()
# AzureCliCredential.get_token expects scopes as positional args
azure_token = credential.get_token(scope[0])
token_result = {
tests/test_core/test_fab_auth_azure_cli.py:37
- The
auth_instancefixture tries to reset theFabAuthsingleton via__wrapped__, butFabAuthis a custom@singletonwrapper (a closure) and neitherFabAuth.__wrapped__norfabric_cli.core.fab_auth.singleton.__wrapped__exist. If this fixture is ever used it will raise AttributeError, and the current tests also risk leaking singleton state between test modules.
Use a robust singleton reset that clears the wrapped closure’s instances dict, ideally as an autouse fixture so all tests in this module get isolation.
This issue also appears on line 39 of the same file.
@pytest.fixture
def auth_instance(temp_dir_fixture):
"""Get a fresh FabAuth instance."""
# Clear singleton for test isolation
FabAuth.__wrapped__ = None # type: ignore
from fabric_cli.core import fab_auth as fab_auth_module
if FabAuth in fab_auth_module.singleton.__wrapped__: # type: ignore
del fab_auth_module.singleton.__wrapped__[FabAuth] # type: ignore
return FabAuth()
…ss calls During login, _get_azure_cli_tenant() was called 4 times (auto-capture + 3 drift checks). Now caches for 30s, reducing to 1 subprocess call. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (4)
src/fabric_cli/core/fab_auth.py:543
_acquire_token_from_azure_clidetermines whether to surface an SDK error message by comparingtype(e).__name__strings, which is brittle (subclasses/renames won’t match) and can unintentionally hide useful Azure SDK diagnostics. Prefer catching/recognizing the concrete Azure exception types viaisinstance.
except Exception as e:
# Allowlist: SDK exceptions are pre-sanitized by azure-identity; unknown exceptions get a safe generic message
if type(e).__name__ in ("ClientAuthenticationError", "HttpResponseError"):
error_msg = str(e)
else:
tests/test_parsers/test_fab_auth_parser.py:6
- Unused import
argparsein this test module.
import argparse
tests/test_core/test_fab_auth_azure_cli.py:26
- This
monkeypatch.setattr(...)call has inconsistent indentation that should be normalized (e.g., by running Black) to keep formatting consistent across the test suite.
# Ensure shutil.which("az") resolves in tests (Windows uses az.cmd)
monkeypatch.setattr(
"shutil.which",
lambda cmd: f"/usr/bin/{cmd}" if cmd == "az" else None,
)
docs/examples/auth_examples.md:94
- The interactive-mode example prompt text doesn’t match the actual menu option string in
fab_auth.init("Azure CLI (existing 'az login' session)"). Keeping them identical helps users follow the docs verbatim.
? How would you like to authenticate Fabric CLI? Azure CLI authentication
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
7ec4ee1 to
2a6ffe1
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (4)
tests/test_core/test_fab_auth_azure_cli.py:26
- The Azure CLI tenant-discovery fixture has inconsistent indentation that will be reformatted by black (and may fail formatting checks as-is).
monkeypatch.setattr(
"shutil.which",
lambda cmd: f"/usr/bin/{cmd}" if cmd == "az" else None,
)
docs/examples/auth_examples.md:95
- The interactive example output doesn't match the actual Azure CLI option string shown in the login menu (currently "Azure CLI (existing 'az login' session)"). Aligning the example avoids confusing copy/paste or screenshot comparisons.
fab auth login
? How would you like to authenticate Fabric CLI? Azure CLI authentication
**src/fabric_cli/core/fab_auth.py:521**
* _acquire_token_from_azure_cli() can raise an IndexError when called with an empty scope list because it later does credential.get_token(scope[0]). Even though callers usually provide a scope, this method is exposed via acquire_token(), so it should fail fast with a structured FabricCLIError when scope is missing/empty, and reuse the computed cache_key when calling get_token.
cache_key = scope[0] if scope else ""
cached = self._get_cached_azure_cli_token(cache_key)
if cached:
return cached
**tests/test_core/test_fab_msal_bridge_azure_cli.py:30**
* This fixture sets config_location() to tmp_path, but if FabAuth() was already instantiated earlier in the test session (singleton), auth.auth_file/cache_file will still point at the original location. Since set_access_mode() may call logout() and write/remove those paths, this can cause tests to touch real user config instead of tmp_path. Patch auth_file/cache_file (and app) here the same way the other Azure CLI auth tests do.
auth = FabAuth()
auth._azure_cli_token_cache.clear()
auth._cached_az_tenant = None
auth._cached_az_tenant_time = 0.0
auth._auth_info = {}
</details>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (4)
src/fabric_cli/core/fab_auth.py:522
- _acquire_token_from_azure_cli() can still raise IndexError when scope is empty because credential.get_token(scope[0]) is called unconditionally, even though cache_key is guarded. This makes the method inconsistent and can crash token acquisition with an unhandled exception.
# Check in-memory cache first
cache_key = scope[0] if scope else ""
cached = self._get_cached_azure_cli_token(cache_key)
if cached:
return cached
try:
credential = (
AzureCliCredential(tenant_id=stored_tenant)
if stored_tenant
else AzureCliCredential()
)
# AzureCliCredential.get_token expects scopes as positional args
azure_token = credential.get_token(scope[0])
token_result = {
src/fabric_cli/core/fab_auth.py:543
- The exception allowlist uses type(e).name string matching and a broad
except Exception as e, which is brittle and could accidentally surface unexpected exception messages. Prefer catching azure-core exceptions explicitly (ClientAuthenticationError/HttpResponseError) and using a separate generic fallback that never includes the raw exception text.
except CredentialUnavailableError:
raise FabricCLIError(
ErrorMessages.Auth.azure_cli_not_available(),
status_code=con.ERROR_AUTHENTICATION_FAILED,
)
except Exception as e:
# Allowlist: SDK exceptions are pre-sanitized by azure-identity; unknown exceptions get a safe generic message
if type(e).__name__ in ("ClientAuthenticationError", "HttpResponseError"):
error_msg = str(e)
else:
error_msg = ErrorMessages.Auth.azure_cli_token_acquisition_failed()
raise FabricCLIError(
ErrorMessages.Auth.azure_cli_auth_failed(error_msg),
status_code=con.ERROR_AUTHENTICATION_FAILED,
)
tests/test_core/test_fab_auth_azure_cli.py:26
- The monkeypatch.setattr call is mis-indented, which is likely to fail formatting/lint checks (and is inconsistent with the rest of the file’s indentation).
monkeypatch.setattr(
"shutil.which",
lambda cmd: f"/usr/bin/{cmd}" if cmd == "az" else None,
)
docs/examples/auth_examples.md:95
- The interactive-mode example doesn’t match the actual menu option string returned by the CLI ("Azure CLI (existing 'az login' session)"). This makes the docs harder to follow and can confuse users trying to verify they selected the right option.
fab auth login
? How would you like to authenticate Fabric CLI? Azure CLI authentication
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (4)
src/fabric_cli/core/fab_auth.py:528
- _acquire_token_from_azure_cli() indexes scope[0] when calling AzureCliCredential.get_token(...). If acquire_token() is invoked with an empty scope list (e.g., via TokenCredential.get_token() being called with no scopes), this will raise IndexError instead of a structured FabricCLIError.
cache_key = scope[0] if scope else ""
cached = self._get_cached_azure_cli_token(cache_key)
if cached:
return cached
src/fabric_cli/core/fab_auth.py:543
- The Azure CLI auth error sanitization relies on checking exception class names (type(e).name). This is brittle and can unintentionally treat non-SDK exceptions as safe. Prefer isinstance checks against azure.core.exceptions types and keep the original exception chained for debugging.
except Exception as e:
# Allowlist: SDK exceptions are pre-sanitized by azure-identity; unknown exceptions get a safe generic message
if type(e).__name__ in ("ClientAuthenticationError", "HttpResponseError"):
error_msg = str(e)
else:
tests/test_core/test_fab_auth_azure_cli.py:26
- This block is not Black-formatted due to inconsistent indentation in the monkeypatch.setattr(...) call. Since CI runs
tox -e lintwith Black, this will fail formatting checks.
monkeypatch.setattr(
"shutil.which",
lambda cmd: f"/usr/bin/{cmd}" if cmd == "az" else None,
)
docs/examples/auth_examples.md:38
- The interactive login example doesn't match the actual menu option string used by the CLI ("Azure CLI (existing 'az login' session)"). This makes the docs harder to follow when users compare to the prompt output.
? How would you like to authenticate Fabric CLI? Azure CLI authentication
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
tests/test_core/test_fab_auth_azure_cli.py:26
- Indentation in the
monkeypatch.setattr(...)block is inconsistent and likely to failblackformatting checks for the tests suite.
monkeypatch.setattr(
"shutil.which",
lambda cmd: f"/usr/bin/{cmd}" if cmd == "az" else None,
)
tests/test_parsers/test_fab_auth_parser.py:9
argparseis imported but unused in this test module, which can trip linting and adds noise.
import argparse
from fabric_cli.core.fab_parser_setup import CustomArgumentParser
from fabric_cli.parsers import fab_auth_parser
…iceResponseError Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (2)
tests/test_core/test_fab_auth_azure_cli.py:26
- The monkeypatch.setattr block is over-indented, which will fail Black formatting and makes the test harder to read. Align the arguments with the opening parenthesis indentation used elsewhere in the repo.
# Ensure shutil.which("az") resolves in tests (Windows uses az.cmd)
monkeypatch.setattr(
"shutil.which",
lambda cmd: f"/usr/bin/{cmd}" if cmd == "az" else None,
)
docs/commands/auth/index.md:55
- The docs list the
-uflag as--user, but the actual parser defines--username. This mismatch will confuse users following the command reference.
- `-u, --user`: Client ID for service principal. Optional.
- `-p, --password`: Client secret for service principal. Optional.
- `--federated-token`: Federated token for workload identity. Optional.
- `--certificate`: Path to certificate file. Optional.
- `--azure-cli`: Use an existing Azure CLI login session as the token provider. Requires Azure CLI to be installed and logged in (`az login`). Optional.
…ntial Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (3)
tests/test_core/test_fab_auth_azure_cli.py:26
- The monkeypatch
setattrblock has inconsistent indentation; runningblackwould reformat this file and may cause formatting-check CI failures if the project enforcesblack --check.
monkeypatch.setattr(
"shutil.which",
lambda cmd: f"/usr/bin/{cmd}" if cmd == "az" else None,
)
tests/test_parsers/test_fab_auth_parser.py:9
- Unused import:
argparseis imported but never referenced in this test module, which adds noise and can trigger unused-import linting in some environments.
import argparse
from fabric_cli.core.fab_parser_setup import CustomArgumentParser
from fabric_cli.parsers import fab_auth_parser
docs/commands/auth/index.md:56
- The parameter list still documents the client-id flag as
-u, --user, but the parser actually defines-u, --username. This mismatch will confuse users following the command reference.
- `--azure-cli`: Use an existing Azure CLI login session as the token provider. Requires Azure CLI to be installed and logged in (`az login`). Optional.
- `--tenant`: Tenant ID. Optional. When used with `--azure-cli`, pins Fabric CLI to the specified tenant.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (2)
tests/test_core/test_fab_auth_azure_cli.py:26
- This
monkeypatch.setattr(...)block has inconsistent indentation compared to other tests (e.g.tests/test_core/test_context_persistence.py:96-98) and is not Black-formatted, which can cause style checks to fail.
monkeypatch.setattr(
"shutil.which",
lambda cmd: f"/usr/bin/{cmd}" if cmd == "az" else None,
)
tests/test_parsers/test_fab_auth_parser.py:7
- Unused import:
argparseis not referenced anywhere in this test file; it should be removed to avoid lint failures and keep imports minimal.
import argparse
…voke MSAL Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (2)
docs/commands/auth/index.md:55
- Docs list the login flag as
-u, --user, but the CLI parser defines-u, --username. This makes the command reference misleading for users trying to use service principal or managed identity client IDs.
- `-u, --user`: Client ID for service principal. Optional.
- `-p, --password`: Client secret for service principal. Optional.
- `--federated-token`: Federated token for workload identity. Optional.
- `--certificate`: Path to certificate file. Optional.
- `--azure-cli`: Use an existing Azure CLI login session as the token provider. Requires Azure CLI to be installed and logged in (`az login`). Optional.
tests/test_core/test_fab_auth_azure_cli.py:26
- The
monkeypatch.setattr("shutil.which", ...)block is not Black-formatted (extra indentation on the wrapped arguments). CI enforces Black via pre-commit (see.github/instructions/test.instructions.md:51), so this may fail formatting checks.
monkeypatch.setattr(
"shutil.which",
lambda cmd: f"/usr/bin/{cmd}" if cmd == "az" else None,
)
…ic-cicd) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (5)
tests/test_core/test_fab_auth_azure_cli.py:26
- The
monkeypatch.setattrcall has inconsistent indentation (extra leading spaces) which is likely to be reformatted byblack/ fail formatting checks.
monkeypatch.setattr(
"shutil.which",
lambda cmd: f"/usr/bin/{cmd}" if cmd == "az" else None,
)
tests/test_parsers/test_fab_auth_parser.py:6
argparseis imported but never used in this test module; this is dead code and can fail linting.
import argparse
src/fabric_cli/core/fab_auth.py:513
_acquire_token_from_azure_cliunconditionally usesscope[0]later (for both caching andcredential.get_token(scope[0])). If this method is ever called with an empty scope list (e.g., viaMsalTokenCredential.get_token()invoked with no scopes), it will raiseIndexErrorinstead of a structuredFabricCLIError.
cache_key = scope[0] if scope else ""
cached = self._get_cached_azure_cli_token(cache_key)
if cached:
return cached
pyproject.toml:23
- PR description says the dependency change is
azure-identity>=1.15.0, butpyproject.tomlsetsazure-identity>=1.25.0. Please reconcile the minimum supported version (either update the PR description or adjust the constraint).
"azure-identity>=1.25.0",
docs/commands/auth/index.md:56
- The parameters list documents the client-id flag as
--user(line 51), but the actual CLI flag is--username(seesrc/fabric_cli/parsers/fab_auth_parser.py). This will mislead users copying the usage docs.
- `--azure-cli`: Use an existing Azure CLI login session as the token provider. Requires Azure CLI to be installed and logged in (`az login`). Optional.
- `--tenant`: Tenant ID. Optional. When used with `--azure-cli`, pins Fabric CLI to the specified tenant.
Summary
Add
azure_clias a new identity type that delegates token acquisition to Azure CLI viaazure-identity'sAzureCliCredential. This allows tools callingfabto reuse an existingaz loginsession instead of requiring a separate interactivefab auth login.Changes
Core auth (
src/fabric_cli/core/fab_auth.py)azure_clitoAUTH_KEYSidentity type allowlist_acquire_token_from_azure_cli()usingAzureCliCredentialaz account showaz account showcalls during multi-scope operationsset_azure_cli()clears token cache on every login to prevent cross-tenant stale tokensshutil.which("az")for Windows compatibility (az.cmdresolution)Command handler (
src/fabric_cli/commands/auth/fab_auth.py)--azure-cliflag tofab auth loginauth_sourceinfab auth statusoutputError handling (
src/fabric_cli/errors/auth.py)ClientAuthenticationError,HttpResponseError,ServiceRequestError,ServiceResponseErrorsurfaced verbatim (pre-sanitized by azure-identity SDK)Documentation
docs/commands/auth/index.md—--azure-cliflag in command reference with separate usage blocks per auth methoddocs/examples/auth_examples.md— Azure CLI auth examples with tenant behavior noteDependencies
azure-identity>=1.15.0Tests
43 automated tests across 4 files:
test_fab_auth_azure_cli.pytest_fab_auth_parser.py--azure-cliflag mappingtest_fab_msal_bridge_azure_cli.pyMsalTokenCredentialdispatch with azure_cli identitytest_fab_auth_command_azure_cli.pyKey test scenarios:
AzureCliCredential, azure_cli doesn't invoke MSALaz.cmdresolution viashutil.whichSecurity
shutil.which("az")resolves absolute path before subprocess (matches azure-identity SDK pattern)shell=Falsefor all subprocess calls — no injection riskOpen questions for team
az logoutconfig persistence: When Azure CLI session is interrupted and restored with the same tenant, fab silently resumes. Should fab require explicit re-authentication instead?